/*
* Copyright (c) 2011 Connection Road.  All rights reserved.
* This program and the accompanying material are made available
* under the terms of the Eclipse Public Licnese v1.0 which is
* available at http://www.eclipse.org/legal/epl-v10.html
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using CrSyncFilesUtils.Enums;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Files;
using System.IO;
using System.Threading;
using Timer = System.Timers.Timer;

namespace CrSyncFilesUtils
{
    /// <summary>
    /// Class for handling local file synchronization ( file to file and file to local network ).
    /// </summary>
    public class FileSynchronizerBase : IFileSynchronizer
    {


        /// <summary>
        /// Initializes object with a default list of folder pairs to synchronize.
        /// </summary>
        /// <param name="folderPairs"></param>
        /// <param name="previewMode"></param>
        public FileSynchronizerBase(List<FileSyncPairInfo> folderPairs, bool previewMode)
        {
            Init();
            PreviewMode = previewMode;
            FolderPairs = folderPairs;
        }

        public FileSynchronizerBase()
        {
            Init();
        }


        // Lists of Folder pair's UID to skip. Add a folder pair to skipp during detecteded changes error or when an error occurs to a particular pair during preview
        protected List<string> _folderPairsToSkip;

        /// <summary>
        /// If this is a non-zero, then event fires every (this many) milliseconds. FileSynUpdateUIWithProgressIng.
        /// </summary>
        protected int UpdateStatsInterval { get; set; }

        /// <summary>
        /// To be inserted in progress events to slow down the processing (debugging and demo only)
        /// </summary>
        protected int PauseTimeMs { get; set; }

        /// <summary>
        ///  Files to exclude from synchronizing
        /// </summary>
        public List<CrSyncedFileInfo> CrSynchedFileInfoToExclude { get; set; }

        /// <summary>
        /// The default orhcestrator
        /// </summary>
        protected SyncOrchestrator _syncOrchestrator;

        // Current providers
        protected SyncProvider _sourceProvider;
        protected SyncProvider _destinationProvider;

        // Current synced stats  / afffected file
        protected CrSyncFileStatistics _currentSyncStats;   // File statistics
        protected CrSyncedFileInfo _currentSyncedFileInfo;  // Information on the current file being synced ( paths, file names etc )
        protected int _skippedFilesCounter;
        protected int _skippedOprationsCounter;
        protected List<CrSyncSkippChangeInfo> _skippedFilesDueToErrors;

        // Current conflicting items
        protected CrSyncConflictingItem _sourceConflictingItem;
        protected CrSyncConflictingItem _destinationConflictingItem;
        protected bool _currentItemIsInConflict;


        /// <summary>
        /// Total number of files changed
        /// </summary>
        protected int _filesChangedCounter;




        /// <summary>
        /// Stores objects that were being deleted / already deleted.
        /// Use the folder pair's unique identifier as the key and 
        /// the relative path as the object being deleted.
        /// The reason for caching deleted objects is that, there were
        /// instances that the same file/folder were detected as being deleted twice.
        /// We just want to make sure that the deleted file/folder count is accurate.
        /// </summary>
        protected Dictionary<string, List<string>> _deletedObjects;

        // Stores the total bytes being deleted.
        // Helps on getting the correct bytes that were being changed.
        protected long _totalBytesDeleted;

        /// <summary>
        /// Holds the total percentage done on all files that were being synchronized.
        // Should only contain between 0 to 100.
        /// </summary>
        public int Progress { get; protected set; }

        /// <summary>
        /// Need to make sure that only one thread can update _Progress at a time or we will have thread crash
        /// </summary>
        protected int ProgressValue
        {
            get
            {
                lock (_lockerPending)
                {
                    return Progress;
                }
            }
            set
            {
                lock (_lockerPending)
                {
                    Progress = value;
                }
            }
        }


        /// <summary>
        /// Indicates weather the current instance is in preview.
        /// </summary>
        public bool PreviewMode { get; set; }

        /// <summary>
        /// Current Folder Pair
        /// </summary>
        protected FileSyncPairInfo _currentFolderPair;

        // Count holders for statistics
        protected int _totalSubfolders;
        protected int _totalFileActionsTaken;

        public int TotalActionsTaken { get; set; }  // Total actions taken when preview mode is set to false.
        public int PreviewedActionsTaken { get; set; }  // The total actions taken in preview mode.
        public int TotalFilesFound { get; set; }
        public int TotalDirectoriesFound { get; set; }
        public long TotalFileSize { get; set; }
        public long TotalBytesToCopy { get; set; }

        public CrSyncFileCountHolder TotalDeletedFolders { get; set; }
        public CrSyncFileCountHolder TotalDeletedFiles { get; set; }
        public CrSyncFileCountHolder TotalRenamedFiles { get; set; }
        public CrSyncFileCountHolder TotalCreatedFolders { get; set; }
        public CrSyncFileCountHolder TotalCreatedFiles { get; set; }
        public CrSyncFileCountHolder TotalOverwrittenFiles { get; set; }
        public CrSyncFileCountHolder TotalSkippedFiles { get; set; }

        public CrSyncFileCountHolder PreviewedDeletedFolders { get; set; }
        public CrSyncFileCountHolder PreviewedDeletedFiles { get; set; }
        public CrSyncFileCountHolder PreviewedRenamedFiles { get; set; }
        public CrSyncFileCountHolder PreviewedCreatedFolders { get; set; }
        public CrSyncFileCountHolder PreviewedCreatedFiles { get; set; }
        public CrSyncFileCountHolder PreviewedOverWrittenFiles { get; set; }
        public CrSyncFileCountHolder PreviewedSkippedFiles { get; set; }

        /// <summary>
        /// Holds the files that were previewed
        /// </summary>
        public List<CrSyncedFileInfo> PreviewedFiles { get; set; }

        /// <summary>
        /// Files that were being skipped by sync framework due to errors.
        /// </summary>
        public List<CrSyncSkippChangeInfo> SkippedFiles
        {
            get { return _skippedFilesDueToErrors; }
        }

        // Temporary sync action mode holder for the current folder pair
        protected FolderPairActionTypeEnum _currentActionType;

        protected Timer _timer;

        /// <summary>
        /// Current folder pairs
        /// </summary>
        public List<FileSyncPairInfo> FolderPairs { get; set; }

        // Need to make sure that only one thread can update CancelPending at a time or we will have thread crash
        protected object _lockerPending;
        protected bool _cancelPending;
        public bool CancelPending
        {
            get
            {
                lock (_lockerPending)
                {
                    return _cancelPending;
                }
            }
            set
            {
                lock (_lockerPending)
                {
                    _cancelPending = value;
                }
            }
        }



        /// <summary>
        /// Deal with anything happening during the file syncing ( progress reporting );
        /// Also this only gets called during an actual Synchronization process or when preview mode is set to false
        /// </summary>
        public event FileSyncEventHandler FilesSyncing;

        /// <summary>
        /// Raises the Files Syncing event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFilesSyncing(FileSyncEventArgs e)
        {
            if (FilesSyncing != null)
            {
                FilesSyncing(this, e);
            }
        }

        /// <summary>
        /// Deal with anything happening during the file syncing ( Preview Mode );
        /// Also this only gets called during an preview Synchronization process or when preview mode is set to true
        /// </summary>
        public event FileSyncEventHandler PreviewingSync;

        /// <summary>
        /// Raises the file syncing preview event
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFilesSyncingPreview(FileSyncEventArgs e)
        {
            if (PreviewingSync != null)
            {
                PreviewingSync(this, e);
            }
        }

        /// <summary>
        /// Deal with the file syncing completed
        /// </summary>
        public event FileSyncEventHandler FilesSyncingCompleted;

        /// <summary>
        /// Raises the Files Syncing event when completed
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFilesSyncingCompleted(FileSyncEventArgs e)
        {
            if (FilesSyncingCompleted != null)
            {
                FilesSyncingCompleted(this, e);
            }
        }

        /// <summary>
        /// Deal with the start event
        /// </summary>
        public event FileSyncEventHandler FileSyncStarted;

        /// <summary>
        ///  Raises the start event 
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFileSyncStarted(FileSyncEventArgs e)
        {
            if (FileSyncStarted != null)
            {
                FileSyncStarted(this, e);
            }
        }


        /// <summary>
        /// Raises a verbose event messages. Useful for writing to console.
        /// </summary>
        public event FileSyncMessageEventHandler VerboseMessages;
        protected virtual void OnVerboseMessages(FileSyncMessageEventArgs e)
        {
            if (VerboseMessages != null)
            {
                VerboseMessages(this, e);
            }
        }

        /// <summary>
        /// Raises a log event messages. Useful for writing events information to a file.
        /// </summary>
        public event FileSyncMessageEventHandler LogMessages;
        protected virtual void OnLogMessages(FileSyncMessageEventArgs e)
        {
            if (LogMessages != null)
            {
                LogMessages(this, e);
            }
        }

        /// <summary>
        /// Deals with errors on each pair during synchronization
        /// </summary>
        public event FileSyncErrorEventHandler FolderPairSyncingError;
        protected virtual void OnFolderPairSyncingError(FileSyncErrorEventArgs e)
        {
            if (FolderPairSyncingError != null)
            {
                FolderPairSyncingError(this, e);
            }
        }

        public event FileSyncErrorEventHandler FileSyncingError;
        protected virtual void OnFileSyncingError(FileSyncErrorEventArgs e)
        {
            if (FileSyncingError != null)
            {
                FileSyncingError(this, e);
            }
        }

        // Timer callbacks
        void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {

        }

        // Convenience method for initializing variables
        protected void Init()
        {
            CrSynchedFileInfoToExclude = new List<CrSyncedFileInfo>();
            PreviewMode = false;
            ResetOperationCounters();
            ResetTotalActionTaken();
            UpdateStatsInterval = 0;
            PreviewedFiles = new List<CrSyncedFileInfo>();
            _lockerPending = new object();
            _skippedFilesDueToErrors = new List<CrSyncSkippChangeInfo>();
        }

        /// <summary>
        /// Convenience method to reset all the stats
        /// </summary>
        public void ResetOperationCounters()
        {

            TotalDeletedFolders = new CrSyncFileCountHolder();
            TotalDeletedFiles = new CrSyncFileCountHolder();
            TotalRenamedFiles = new CrSyncFileCountHolder();
            TotalCreatedFiles = new CrSyncFileCountHolder();
            TotalCreatedFolders = new CrSyncFileCountHolder();
            TotalOverwrittenFiles = new CrSyncFileCountHolder();
            TotalSkippedFiles = new CrSyncFileCountHolder();

            PreviewedDeletedFolders = new CrSyncFileCountHolder();
            PreviewedDeletedFiles = new CrSyncFileCountHolder();
            PreviewedRenamedFiles = new CrSyncFileCountHolder();
            PreviewedCreatedFiles = new CrSyncFileCountHolder();
            PreviewedCreatedFolders = new CrSyncFileCountHolder();
            PreviewedOverWrittenFiles = new CrSyncFileCountHolder();
            PreviewedSkippedFiles = new CrSyncFileCountHolder();

            ResetDetectedFilesCounter();

            _folderPairsToSkip = new List<string>();

        }

        /// <summary>
        /// Sets all detected file counters to zero.
        /// </summary>
        public void ResetDetectedFilesCounter()
        {
            TotalFilesFound = 0;
            TotalDirectoriesFound = 0;
            TotalFileSize = 0;
            TotalBytesToCopy = 0;
            _totalSubfolders = 0;
        }

        /// <summary>
        /// Convenience method to reset the total action taken
        /// </summary>
        public void ResetTotalActionTaken()
        {
            TotalActionsTaken = 0;
            _totalFileActionsTaken = 0;
            PreviewedActionsTaken = 0;
        }


        /// <summary>
        /// Helper method for console output
        /// </summary>
        /// <param name="message"></param>
        /// <param name="useAsLog">If set to true, then the message will also appears on the log callbacks</param>
        /// <exception cref="ApplicationException"></exception>
        protected virtual void VerboseOut(string message, bool useAsLog)
        {
            try
            {
                var verboseEvent = new FileSyncMessageEventArgs(string.Format(message), PreviewMode);
                OnVerboseMessages(verboseEvent);

                if (useAsLog)
                {
                    var logEvent = new FileSyncMessageEventArgs(string.Format(message), PreviewMode);
                    OnLogMessages(logEvent);
                }
            }
            catch (FormatException e)
            {
                Console.WriteLine(@"FormatException: " + e);
            }
        }

        /// <summary>
        /// Process a list of folder pairs at once.
        /// </summary>
        /// <param name="folderPairs"></param>
        /// <returns></returns>
        public virtual string ProcessFileSyncPairInfos(List<FileSyncPairInfo> folderPairs)
        {
            try
            {
                _filesChangedCounter = 0;
                ResetDetectedFilesCounter();
                VerboseOut(string.Format("[ PROCESS FILE SYNC PAIR INFO ]---------------------------------"), true);
                VerboseOut(string.Format("Synchronizing {0} folder pair(s)...", folderPairs.Count), true);

                // Fire a start event
                if (PreviewMode)
                {
                    // Fire an a preview start event
                    const string startMessage = "Detecting changes. This may take a while. Please wait...";
                    var syncStartEvent = new FileSyncEventArgs(startMessage, PreviewMode);
                    OnFileSyncStarted(syncStartEvent);
                    VerboseOut("[PREVIEW MODE] " + startMessage, false);
                }
                else
                {
                    // Fire an actual start event
                    const string startMessage = "Synchronization started...";
                    var syncStartEvent = new FileSyncEventArgs(startMessage, PreviewMode);
                    OnFileSyncStarted(syncStartEvent);
                }

                foreach (var folderPair in folderPairs)
                {
                    TryCatchProcessFileSyncPairInfo(folderPair);  // Sync current folder pair
                }

                ProgressValue = 100;
                VerboseOut(" ", true);
                VerboseOut(string.Format("    Actions Taken {0}", (PreviewMode) ? PreviewedActionsTaken : TotalActionsTaken), true);
                VerboseOut("Synchronization process ended.", false);
                VerboseOut(" ", true);
                VerboseOut("[ END OF PROCESS FILE SYNC PAIR INFO ]---------------------------------", true);
                VerboseOut(" ", true);

                // Fire a sync complete event
                CrSyncFileStatistics crSyncFileStatistics;

                if (PreviewMode)
                {
                    crSyncFileStatistics = new CrSyncFileStatistics(PreviewedDeletedFolders,
                                                                   PreviewedDeletedFiles,
                                                                   PreviewedRenamedFiles,
                                                                   PreviewedCreatedFolders,
                                                                   PreviewedCreatedFiles,
                                                                   PreviewedOverWrittenFiles,
                                                                   PreviewedSkippedFiles,
                                                                   TotalBytesToCopy) { Progress = ProgressValue };
                }
                else
                {
                    crSyncFileStatistics = new CrSyncFileStatistics(TotalDeletedFolders,
                                                                    TotalDeletedFiles,
                                                                    TotalRenamedFiles,
                                                                    TotalCreatedFolders,
                                                                    TotalCreatedFiles,
                                                                    TotalOverwrittenFiles,
                                                                    TotalSkippedFiles,
                                                                    TotalBytesToCopy) { Progress = ProgressValue };
                }

                var syncCompletedEvent = new FileSyncEventArgs(crSyncFileStatistics, "Completed") { PreviewMode = PreviewMode };
                OnFilesSyncingCompleted(syncCompletedEvent);

                var message = string.Format("Completed.  Files Changed {0}", _filesChangedCounter);
                VerboseOut(message, false);

                return message;
            }
            catch (SyncAbortedException)
            {
                //Exit gracefully
                const string message = "The Synchronization was cancelled by the user...";
                VerboseOut(message, false);
                return message;
            }
            catch (SyncException e)
            {
                // Verbose event handling
                VerboseOut(string.Format("[ERROR]-------------------"), true);
                VerboseOut("     MESSAGE : "+e.Message, true);
                if (!string.IsNullOrEmpty(e.StackTrace))
                {
                    VerboseOut("---------- STACK -----------\r\n\r\n"+e.StackTrace+"\r\n\r\n", true);
                }
                var message = string.Format("Completed.  Files Changed {0}", _filesChangedCounter);
                VerboseOut(string.Format("[END OF ERROR]--------------------"), true);

                // Error event handling
                var errorMessage = string.Format("[ERROR] CRSyncFiles failed to synchronize the following Folder Pair: {0}. {1} is causing this issue.", FolderPairs.Count, e.Message);
                var errorEvent = new FileSyncErrorEventArgs(errorMessage, e)
                                     {
                                         ExceptionType = CrSyncExceptionTypes.SyncException
                                     };
                OnFileSyncingError(errorEvent);
                return message;
            }
            catch(Exception generalException)
            {
                // Verbose event handling
                VerboseOut(string.Format("[ERROR]-------------------"), true);
                VerboseOut("     MESSAGE : "+generalException.Message, true);
                if (!string.IsNullOrEmpty(generalException.StackTrace))
                {
                    VerboseOut("---------- STACK -----------\r\n\r\n"+generalException.StackTrace+"\r\n\r\n", true);
                }
                var message = string.Format("Completed.  Files Changed {0}", _filesChangedCounter);
                VerboseOut(string.Format("[END OF ERROR]--------------------"), true);

                // Error event handling
                var errorMessage = string.Format("[ERROR] CRSyncFiles failed to synchronize the following Folder Pair: {0}. {1} is causing this issue.", FolderPairs.Count, generalException.Message);
                var errorEvent = new FileSyncErrorEventArgs(errorMessage, generalException)
                                     {
                                         ExceptionType = CrSyncExceptionTypes.GenericException
                                     };
                OnFileSyncingError(errorEvent);
                return message;
            }

        }

        /// <summary>
        /// Process single folder pair and cathces known exceptions.
        /// Override this method if you want to perform tasks before a folder pair
        /// is being synced and checked for any exceptions.
        /// </summary>
        /// <param name="folderPair"></param>
        /// <exception cref="SyncAbortedException">SyncAbortedException</exception>
        /// <exception cref="COMException">COMException</exception>
        /// <exception cref="Exception">Exception</exception>
        protected virtual void TryCatchProcessFileSyncPairInfo(FileSyncPairInfo folderPair)
        {
            // Initialize deleleted objects list
            _deletedObjects = new Dictionary<string, List<string>>();

            // Initialize total deleted bytes
            _totalBytesDeleted = 0;


            try
            {
                ProcessFileSyncPairInfo(folderPair);
            }
            catch (NotImplementedException exception)
            {
                VerboseOut("[SUPPRESSING ERROR : NotImplementedException]-------------------", false);
                VerboseOut(string.Format("REASON : {0}", exception.Message), false);
            }
            catch (SyncAbortedException)
            {
                // Suppress per folder pair error event by rethrow for the parent try-catch to perform necessary events;
                throw;
            }
            catch (COMException)
            {
                // Treat this error as sync aborted
                throw new SyncAbortedException("Founc COMException and treated it as SyncAborted...");
            }
            catch (Exception currentPairSyncException)
            {
                // Here we catch errors and exception returned by the InnerException.

                // Rethrows and lets the top level try-catch handle it, if the error is the sync being cancelled.
                if ((currentPairSyncException.InnerException != null) && (currentPairSyncException.InnerException.GetType() == typeof(SyncAbortedException)))
                {
                    var syncAbortedException = (SyncAbortedException)currentPairSyncException.InnerException;
                    throw syncAbortedException;
                }

                // Rethrows and lets the top level try-catch handle for any COMException and treat it SyncAbortedException
                if ((currentPairSyncException.InnerException != null) && (currentPairSyncException.InnerException.GetType() == typeof(COMException)))
                {
                   throw new SyncAbortedException("The Synchronization was cancelled by the user.");
                }

                // Also suppress further errors that points to being not yet implemented exception...
                if ((currentPairSyncException.InnerException != null) && (currentPairSyncException.InnerException.GetType() == typeof(NotImplementedException)))
                {
                    VerboseOut("[ SUPPRESSING ERROR : NotImplementedException]-------------------", false);
                    VerboseOut(string.Format("REASON : {0}", currentPairSyncException.Message), false);

                }
                else
                {
                    // Add current pair for exclusion on the next sync
                    SkipFolderPairWithUid(_currentFolderPair.UniqueIdentifier);

                    // Verbose event handling
                    VerboseOut(string.Format("[SYNC ERROR : --------------------"), true);
                    VerboseOut(string.Format("     PAIR NAME : {0}", folderPair.FolderPairName), true);
                    VerboseOut("     MESSAGE : " + currentPairSyncException.Message, true);
                    if (!string.IsNullOrEmpty(currentPairSyncException.StackTrace))
                    {
                        VerboseOut("---------- STACK ---------\r\n\r\n"+currentPairSyncException.StackTrace+"\r\n\r\n", true);
                    }

                    VerboseOut(string.Format("[END OF SYNC ERROR]--------------------"), true);

                    // Error event handling
                    var errorMessage = string.Format("[SYNC ERROR] Failed to synchronize Folder Pair {0}, {1}",
                                                folderPair.FolderPairName, currentPairSyncException.Message);

                    var errorEvent = new FileSyncErrorEventArgs(errorMessage, currentPairSyncException)
                                         {
                                             FolderPair = folderPair,
                                             ExceptionType = CrSyncExceptionTypes.GenericException
                                         };
                    OnFolderPairSyncingError(errorEvent);
                }


            }
        }


        /// <summary>
        /// Process single folder pair.
        /// Override this method if you want to catch exceptions other than the currently known exceptions.
        /// Known exceptions being handled: Exception ( Generic ), SyncAbortedException, COMException and NotImplementedException
        /// </summary>
        /// <param name="folderPair"></param>
        protected virtual void ProcessFileSyncPairInfo(FileSyncPairInfo folderPair)
        {
            // Check and skip folder pair
            if (!_folderPairsToSkip.Contains(folderPair.UniqueIdentifier))
            {
                var fileSyncPairInfo = folderPair;
                var sourceReplica = fileSyncPairInfo.LeftFolder;
                var destinationReplica = fileSyncPairInfo.RightFolder;

                // Store current pair as global
                _currentFolderPair = fileSyncPairInfo;

                // Current Sync Mode 
                _currentActionType = folderPair.FolderPairActionType;


                // Verbose out
                VerboseOut("about to do DetectChanges...", false);
                VerboseOut("Starting synchronization process...", false);
                VerboseOut("Depending on the number of files this could take a while...", false);
                VerboseOut("Please wait...", false);
                VerboseOut(" ", true);
                VerboseOut(" ", true);
                VerboseOut(string.Format("    FOLDER PAIR: {0}", folderPair.FolderPairName), true);
                VerboseOut(string.Format("    ACTION TYPE: {0}", folderPair.FolderPairActionType), true);
                VerboseOut(string.Format("    RUN ALL: {0}", (folderPair.ActiveForRunAll) ? "Active" : " Not active"), true);
                VerboseOut("    FOLDER PAIR OPTIONS:", true);
                VerboseOut(string.Format("         INCLUDE FILES: {0}", (folderPair.GetSearchPattern() == "*") ? "All files" : folderPair.GetSearchPattern()), true);
                VerboseOut(string.Format("         EXCLUDE FILES: {0}", string.IsNullOrEmpty(folderPair.GetExcludePattern()) ? "None" : folderPair.GetExcludePattern()), true);
                VerboseOut(string.Format("         EXCLUDE SUB FOLDERS: {0}", string.IsNullOrEmpty(folderPair.SubFoldersToExclude) ? "All subfolders included" : folderPair.SubFoldersToExclude), true);
                VerboseOut(string.Format("         {0}", folderPair.OptionsCompareFileStreams ? "CHECK FILE CONTENTS" : "DO NOT CHECK FILE CONTENTS"), true);
                VerboseOut(string.Format("         {0}", folderPair.FileExcludeAttrReadOnly ? "EXCLUDE READ-ONLY FILES" : "INCLUDE READ-ONLY FILES"), true);
                VerboseOut(string.Format("         {0}", folderPair.FileExcludeAttrHidden ? "EXCLUDE HIDDEN FILES" : "INCLUDE HIDDEN FILES"), true);
                VerboseOut(string.Format("         {0}", folderPair.FileExcludeAttrSystem ? "EXCLUDE SYSTEM FILES" : "INCLUDE SYSTEM FILES"), true);
                VerboseOut(string.Format("         {0}", folderPair.OptionsRecycleDeletedFiles ? "RECYCLE DELETED FILES (send to recycle bin)" : "DO NOT RECYCLE DELETED FILES"), true);


                DetectChangesOnReplica(folderPair, sourceReplica, PreviewMode);
                DetectChangesOnReplica(folderPair, destinationReplica, PreviewMode);

                VerboseOut(string.Format("    LEFT FOLDER: {0}", folderPair.LeftFolder.Path), true);
                VerboseOut(string.Format("    RIGHT FOLDER: {0}", folderPair.RightFolder.Path), true);
                VerboseOut(string.Format("        File(s) = {0}", TotalFilesFound), true);
                VerboseOut(string.Format("        Directory(s) = {0}", TotalDirectoriesFound), true);
                VerboseOut(string.Format("        File size = {0}\n", TotalFileSize), true);

                VerboseOut(" ", true);


                // Sync in both directions
                if (!CancelPending)
                {
                    SyncFileSystemReplicas(folderPair, sourceReplica, destinationReplica, PreviewMode);
                    VerboseOut("SyncFileSystemReplicas 1->2 done", false);

                    if (!CancelPending)
                    {
                        SyncFileSystemReplicas(folderPair, destinationReplica, sourceReplica, PreviewMode);
                        VerboseOut("SyncFileSystemReplicas 2->1 done", false);
                    }
                }

            }
            else
            {
                VerboseOut("A Folder Pair with UID " + folderPair.UniqueIdentifier + " has been skipped from synchronizing.", false);
            }
        }


        /// <summary>
        /// Starts the synchronization process.
        /// 
        /// </summary>
        /// <param name="pauseTimeMs">
        /// Defaults to 1000.
        /// update screen with totals every second (better if this was more like 100 ms)
        /// this will cause a timer ot be run every 1000 milliseconds that will cause the 
        /// FileSynchronizer_FileSynUpdateUIWithProgressIng method to be called trigger the event FileSynUpdateUIWithProgressIng
        /// to be fired giving control to this dialog the ability to update all the totals.  All the totals are passed in the args.
        /// </param>
        /// <param name="updateStatsInterval"> 
        /// Defaults to 250.
        /// this inserts an artificial pause in progress events so that it's easier to debug and demostrate.
        /// </param>
        /// <returns></returns>
        public void Synchronize(int updateStatsInterval = 1000, int pauseTimeMs = 250)
        {
            UpdateStatsInterval = updateStatsInterval;
            PauseTimeMs = pauseTimeMs;


            if (UpdateStatsInterval != 0)
            {
                _timer = new Timer { Interval = UpdateStatsInterval };
                _timer.Elapsed += TimerElapsed;
                _timer.Start();
            }

            ProcessFileSyncPairInfos(FolderPairs);
        }

        public void Stop()
        {
            CancelPending = true;
        }




        /// <summary>
        /// Synchronizes replica in a one-way direction.
        /// </summary>
        /// <param name="fileSyncPairInfo"></param>
        /// <param name="sourceLocation"></param>
        /// <param name="destinationLocation"></param>
        /// <param name="previewMode"></param>
        /// <param name="synchronize"></param>
        protected virtual void SyncFileSystemReplicas(FileSyncPairInfo fileSyncPairInfo, ICrSyncLocation sourceLocation, ICrSyncLocation destinationLocation, bool previewMode, bool synchronize = true)
        {
            _sourceProvider = null;
            _destinationProvider = null;

            // Create sync provider for each location.
             var sourceProvider = new FileSyncProvider(new Guid(sourceLocation.UniqueIdentifier),
                                                     sourceLocation.Path,
                                                     fileSyncPairInfo.GetFileSyncScopeFilter(),
                                                     fileSyncPairInfo.GetFileSyncOptions(),
                                                     sourceLocation.MetaDataDirectory,
                                                     sourceLocation.FileName,
                                                     sourceLocation.MetaDataDirectory,
                                                     null) { PreviewMode = previewMode };
            var destinationProvider = new FileSyncProvider(new Guid(destinationLocation.UniqueIdentifier),
                                                     destinationLocation.Path,
                                                     fileSyncPairInfo.GetFileSyncScopeFilter(),
                                                     fileSyncPairInfo.GetFileSyncOptions(),
                                                     destinationLocation.MetaDataDirectory,
                                                     destinationLocation.FileName,
                                                     destinationLocation.MetaDataDirectory,
                                                     null) { PreviewMode = previewMode };

            _sourceProvider = sourceProvider;
            _destinationProvider = destinationProvider;


            // ------- Soure file provider

            sourceProvider.ApplyingChange += CallBackFileProviderApplyingChange; // About to apply changes to detected folders/files
            sourceProvider.AppliedChange += CallBackFileProviderAppliedChange; // Done applying changes to files/folders
            sourceProvider.CopyingFile += CallBackFileProviderCopyingFile; // File is being copied
            sourceProvider.SkippedChange += CallBackFileProviderSkippedChange; // Skipping changes to detected files/folders
            sourceProvider.SkippedFileDetect += CallBackFileProviderSkippedFileDetect; // Skipped file/folder found

            //sourceProvider.Configuration.CollisionConflictResolutionPolicy = CollisionConflictResolutionPolicy.SourceWins;
            //sourceProvider.Configuration.ConflictResolutionPolicy = ConflictResolutionPolicy.SourceWins;


            // Explicitly detects items that are conflicting each other on both location for Echo and Contribute
            if ((fileSyncPairInfo.FolderPairActionType == FolderPairActionTypeEnum.Echo) || (fileSyncPairInfo.FolderPairActionType == FolderPairActionTypeEnum.Contribute))
            {
                sourceProvider.DestinationCallbacks.ItemConflicting += CallBackFileProviderItemConflicting;
            }

            // ------- Destination file provider
            destinationProvider.ApplyingChange += CallBackFileProviderApplyingChange; // About to apply changes to detected folders/files
            destinationProvider.AppliedChange += CallBackFileProviderAppliedChange; // Done applying changes to files/folders
            destinationProvider.CopyingFile += CallBackFileProviderCopyingFile; // File is being copied
            destinationProvider.SkippedChange += CallBackFileProviderSkippedChange; // Skipping changes to detected files/folders
            destinationProvider.SkippedFileDetect += CallBackFileProviderSkippedFileDetect; // Skipped file/folder found

            //destinationProvider.Configuration.CollisionConflictResolutionPolicy = CollisionConflictResolutionPolicy.SourceWins;
            //destinationProvider.Configuration.ConflictResolutionPolicy = ConflictResolutionPolicy.SourceWins;

            // Explicitly detects items that are conflicting each other on both location for Echo and Contribute
            if ((fileSyncPairInfo.FolderPairActionType == FolderPairActionTypeEnum.Echo) || (fileSyncPairInfo.FolderPairActionType == FolderPairActionTypeEnum.Contribute))
            {
                destinationProvider.DestinationCallbacks.ItemConflicting += CallBackFileProviderItemConflicting;
            }

            

            var destinationRootDirectory = destinationProvider.RootDirectoryPath;

            if (synchronize)
            {
                _syncOrchestrator = new SyncOrchestrator
                {
                    LocalProvider = _sourceProvider,
                    RemoteProvider = _destinationProvider,
                    Direction = SyncDirectionOrder.Upload
                };
                VerboseOut("Synchronizing changes to replica: " + destinationRootDirectory, false);
                _syncOrchestrator.Synchronize();
            }


            // Release resources

            sourceProvider.Dispose();
            VerboseOut("FileSyncProvider object disposed for " + sourceLocation.Path, false);

            destinationProvider.Dispose();
            VerboseOut("FileSyncProvider object disposed for " + destinationLocation.Path, false);
        }

        /// <summary>
        /// Convenience method for gathering various information on one of folder pair's root directory.
        /// This is also the place where we attach the detect changes callbacks on each provider.
        /// </summary>
        protected virtual void DetectChangesOnReplica(FileSyncPairInfo fileSyncPairInfo, ICrSyncLocation location, bool previewMode)
        {
            VerboseOut(string.Format("Getting local files and folders in {0}", location.Path), false);
            var fileSyncProvider = new FileSyncProvider(new Guid(location.UniqueIdentifier),
                                            location.Path,
                                            fileSyncPairInfo.GetFileSyncScopeFilter(),
                                            fileSyncPairInfo.GetFileSyncOptions(),
                                            location.MetaDataDirectory,
                                            location.FileName,
                                            location.MetaDataDirectory,
                                            null) { PreviewMode = previewMode };
            fileSyncProvider.DetectingChanges += CallBackFileProviderDetectingChanges;
            fileSyncProvider.DetectedChanges += CallBackFileProviderDetectedChanges;
            fileSyncProvider.PreviewMode = previewMode;

            fileSyncProvider.DetectChanges();

            fileSyncProvider.Dispose();
            VerboseOut("FileSyncProvider object disposed for " + location.Path, false);
        }

        protected virtual void CallBackFileProviderSkippedChange(object sender, SkippedChangeEventArgs e)
        {
            var affectedFile = e.CurrentFilePath ?? e.NewFilePath;
            VerboseOut("    Skipping \"" + e.ChangeType + "\" on " + affectedFile + " because of " + e.SkipReason, true);

            if (e.Exception != null)
            {
                _skippedFilesDueToErrors.Add(new CrSyncSkippChangeInfo(_currentFolderPair, e));
                var errorMessage = "[ERROR: SkippedChanged]--------------------\r\n\r\n" +
                               "CURRENT FILE : " + e.CurrentFilePath + "\r\n\r\n" +
                               "NEW FILE : " + e.NewFilePath + "\r\n\r\n" +
                               "MESSAGE : " + e.Exception.Message + "\r\n\r\n" +
                               "STACK: \r\n\r\n" + e.Exception.StackTrace;
                VerboseOut(errorMessage, true);



                // Since the framework skipped the changes on the current affected file with exception thrown
                // then we also need to make sure that this skipping gets counted. That is to make sure
                // that the current affected file/folder does not get counted from the stats since the
                // action being performed failed.


                switch (e.ChangeType)
                {
                    case ChangeType.Create:
                        if (_currentSyncedFileInfo.IsDirectory)
                        {
                            _currentSyncStats.TotalCreatedFolders.Add(-1, 1);
                        }
                        else
                        {
                            _currentSyncStats.TotalCreatedFiles.Add(-1, 1);
                        }
                        break;
                    case ChangeType.Delete:
                        if (_currentSyncedFileInfo.IsDirectory)
                        {
                            _currentSyncStats.TotalDeletedFolders.Add(-1, 1);
                        }
                        else
                        {
                            _currentSyncStats.TotalDeletedFiles.Add(-1, 1);
                        }
                        break;
                    case ChangeType.Rename:
                        _currentSyncStats.TotalRenamedFiles.Add(-1, 1);
                        break;
                    case ChangeType.Update:
                        _currentSyncStats.TotalOverWrittenFiles.Add(-1, 1);
                        break;
                    default:
                        break;
                }
            }
        }

        protected virtual void CallBackFileProviderDetectingChanges(object sender, DetectingChangesEventArgs e)
        {
            var message = string.Format("Detecting changes to {0}", e.DirectoryPath);
            VerboseOut(message, false);

            // Count subfolders
            var fileNode = new FileNode(e.DirectoryPath);
            var children = fileNode.ChildNodes(null, true);
            foreach (var depth in
                children.Where(child => child.IsDirectory()).Select(child => child.GetDepthForPath(e.DirectoryPath)).Where(depth => depth > 1))
            {
                _totalSubfolders++;
            }

            // Fire FilesSyncingPreview mode event
            var syncEvent = new FileSyncEventArgs(message, PreviewMode);
            OnFilesSyncingPreview(syncEvent);
        }

        protected virtual void CallBackFileProviderDetectedChanges(object sender, DetectedChangesEventArgs e)
        {
            VerboseOut("File provider detected changes invoked...", false);
            TotalFilesFound += int.Parse(e.TotalFilesFound.ToString());
            TotalDirectoriesFound += int.Parse(e.TotalDirectoriesFound.ToString());
            TotalFileSize += e.TotalFileSize;

        }


        protected virtual void CallBackFileProviderSkippedFileDetect(object sender, SkippedFileDetectEventArgs e)
        {

            VerboseOut("        Skipped file detect on " + e.FilePath + " because of " + e.SkipReason, true);
            if (e.Exception != null)
            {
                var errorMessage = "[ERROR: SkippedFileDetect]--------------------\r\n\r\n" +
                               "FILE : " + e.FilePath + "\r\n\r\n" +
                               "MESSAGE : " + e.Exception.Message + "\r\n\r\n" +
                               "STACK: \r\n\r\n" + e.Exception.StackTrace;
                VerboseOut(errorMessage, true);
            }
        }

        /// <summary>
        /// Almost alwasy gets called during synchronization
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void CallBackFileProviderCopyingFile(object sender, CopyingFileEventArgs e)
        {
            VerboseOut("Copying file " + e.FilePath, false);
            VerboseOut(string.Format("Percent copied {0}", e.PercentCopied), false);

            var currentSyncProvider = sender as FileSyncProvider;
            if (currentSyncProvider != null)
            {
                var currentNode = new FileNode(currentSyncProvider.RootDirectoryPath).Join(e.FilePath);

                long percentCopied = 0;
                if (e.PercentCopied >= 100)
                {
                    percentCopied = 100;

                    if (e.PercentCopied > 100)
                    {
                        VerboseOut("CopyingFileEventArgs returned percent copied more than 100%. value = "+e.PercentCopied,true);
                    }
                }else if (e.PercentCopied < 0)
                {
                    percentCopied = 0;

                    VerboseOut("CopyingFileEventArgs returned percent copied less than 0%. value = " + e.PercentCopied, true);
                }
                else
                {
                    percentCopied = e.PercentCopied;
                }

                var message = percentCopied + "% done coyping " + currentNode.GetFileName();


                // Fire an event that tells what files were being copied but prevent from displaying if the file is a current metadata )
                if ((e.FilePath != _currentFolderPair.LeftFolder.FileName) || (e.FilePath != _currentFolderPair.RightFolder.FileName))
                {
                    var fileSyncEventArgs = new FileSyncEventArgs(message, PreviewMode);
                    OnFilesSyncing(fileSyncEventArgs);
                }


            }

            // Do pause when able
            Thread.Sleep(PauseTimeMs);

            //Check for cancellation
            if (CancelPending)
            {
                _syncOrchestrator.Cancel();
            }
        }

        /// <summary>
        /// Triggers when the provider is performing some changes on the current file. This delegate always get triggered regardless of mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void CallBackFileProviderApplyingChange(object sender, ApplyingChangeEventArgs e)
        {

            VerboseOut("Applying changes to destination path...", false);
            var provider = (FileSyncProvider)sender;
            string sourceDirectory, sourceFileName, sourceRelativeFilePath;
            string destinationDirectory, destinationFileName, destinationRelativeFilePath;
            DateTime sourceLastModified, destinationLastModified;
            bool isDirectory;

            GetAffectedFile(provider,
                            e,
                            _currentFolderPair,
                            out sourceDirectory,
                            out destinationDirectory,
                            out sourceRelativeFilePath,
                            out destinationRelativeFilePath,
                            out sourceLastModified,
                            out destinationLastModified,
                            out sourceFileName,
                            out destinationFileName,
                            out isDirectory);

            var affectedFileInfoMessage = " [ AFFECTED FILE INFO ] PROVIDER = " + provider.RootDirectoryPath + " , SOURCE = " + sourceDirectory + ", DEST = " + destinationDirectory;
            VerboseOut(affectedFileInfoMessage, false);
            VerboseOut("RIGHT FOLDER PAIR " + _currentFolderPair.RightFolder.Path, false);

            /**********************************************************************************************************
             * Perform various events depending on the type of sync mode.
             * This delegate doesn't do stats counting, just reporting stats. All stuffs happen  in 
             * GetSyncedFileInfo(...) method.  All the information we need for the current afffected file 
             * and the statistical analysis are all being done there.
             *********************************************************************************************************/
            switch (_currentActionType)
            {
                case FolderPairActionTypeEnum.Synchronize:

                    CrSyncFileStatistics fileStatistics;
                    GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out _currentSyncedFileInfo);
                    if (PreviewMode)
                    {
                        if (_currentSyncedFileInfo != null)
                        {
                            var fileBeingChanged = _currentSyncedFileInfo.SourceFileName;
                            var syncEvent = new FileSyncEventArgs(fileStatistics,
                                                                                _currentSyncedFileInfo,
                                                                                PreviewMode,
                                                                                "Analyzing changes to " + fileBeingChanged);
                            OnFilesSyncingPreview(syncEvent);
                            PreviewedFiles.Add(_currentSyncedFileInfo);
                        }
                    }
                    else
                    {
                        // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                        // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                        // the stats reporting.
                        _currentSyncStats = fileStatistics;
                    }

                    break;
                case FolderPairActionTypeEnum.Contribute:
                    ApplyingChanges(FolderPairActionTypeEnum.Contribute,
                                    provider,
                                    e,
                                    sourceDirectory,
                                    destinationDirectory,
                                    isDirectory,
                                    out _currentSyncedFileInfo);
                    break;
                case FolderPairActionTypeEnum.Echo:
                    ApplyingChanges(FolderPairActionTypeEnum.Echo,
                                    provider,
                                    e,
                                    sourceDirectory,
                                    destinationDirectory,
                                    isDirectory,
                                    out _currentSyncedFileInfo);
                    break;
                default:
                    VerboseOut("Skipping synced file info reporting because none of the action type could not be identified...", false);
                    VerboseOut("Available action types: SYNCHRONIZE, ECHO, CONTRIBUTE...", false);
                    break;
            }


            // Do pause when able
            Thread.Sleep(PauseTimeMs);

            //Check for cancelation
            if (CancelPending)
            {
                _syncOrchestrator.Cancel();
            }
        }

        /// <summary>
        /// Handles applyng changes for ECHO and CONTRIBUTE ony.
        /// </summary>
        /// <param name="syncType"></param>
        /// <param name="provider"></param>
        /// <param name="e"></param>
        /// <param name="sourceDirectory"></param>
        /// <param name="destinationDirectory"></param>
        /// <param name="isDirectory"></param>
        /// <param name="syncedFileInfo"></param>
        protected virtual void ApplyingChanges(FolderPairActionTypeEnum syncType,
            FileSyncProvider provider,
            ApplyingChangeEventArgs e,
            string sourceDirectory,
            string destinationDirectory,
            bool isDirectory,
            out CrSyncedFileInfo syncedFileInfo)
        {
            // Echo and Contribute is a little trickier to handle. Depending on the location ( Left or right ) , if the affected file is originated
            // from the right then there's no reason to report or even count the stats for that particular file.

            CrSyncFileStatistics fileStatistics;
            FileSyncEventArgs syncEvent;
            string fileBeingChanged;
            syncedFileInfo = null;

            switch (e.ChangeType)
            {
                case ChangeType.Update:
                case ChangeType.Create:
                    if (sourceDirectory == _currentFolderPair.RightFolder.Path)
                    {
                        VerboseOut("Skipping synced file info reporting because ECHO does not allow new and updated files from the right to be copied to the left...", false);
                        e.SkipChange = true;
                    }
                    else if (isDirectory)
                    {

                        // For new folders being created only folders that are being copied from left to right is allowed.
                        var rootNode = new FileNode(destinationDirectory);
                        if (rootNode.IsChildOf(_currentFolderPair.RightFolder.Path))
                        {
                            // Allow if the destination is the right folder
                            GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo); // Get various information on the current affected file

                            if (PreviewMode)
                            {
                                fileBeingChanged = syncedFileInfo.SourceFileName;
                                syncEvent = new FileSyncEventArgs(fileStatistics, syncedFileInfo, PreviewMode, (PreviewMode) ? "Analyzing changes to " : "Applying changes to " + fileBeingChanged);
                                OnFilesSyncingPreview(syncEvent);
                                PreviewedFiles.Add(syncedFileInfo);
                            }
                            else
                            {
                                // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                                // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                                // the stats reporting.
                                _currentSyncStats = fileStatistics;
                            }
                        }
                        else
                        {
                            VerboseOut("Skipping synced file info reporting because ECHO does not allow new folders from the right to be copied to the left...", false);
                            e.SkipChange = true;
                        }
                    }
                    else
                    {

                        GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo); // Get various information on the current affected file

                        if (PreviewMode)
                        {
                            fileBeingChanged = syncedFileInfo.SourceFileName;
                            syncEvent = new FileSyncEventArgs(fileStatistics, syncedFileInfo, PreviewMode, (PreviewMode) ? "Analyzing changes to " : "Applying changes to " + fileBeingChanged);
                            OnFilesSyncingPreview(syncEvent);
                            PreviewedFiles.Add(syncedFileInfo);
                        }
                        else
                        {
                            // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                            // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                            // the stats reporting.
                            _currentSyncStats = fileStatistics;
                        }
                    }
                    break;
                case ChangeType.Rename:
                    if (destinationDirectory == _currentFolderPair.LeftFolder.Path)
                    {
                        VerboseOut("Skipping synced file info reporting because ECHO does not allow renamed files on the right to be copied to the left...", false);
                        e.SkipChange = true;
                    }
                    else
                    {
                        GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo); // Get various information on the current affected file

                        if (PreviewMode)
                        {
                            fileBeingChanged = syncedFileInfo.SourceFileName;
                            syncEvent = new FileSyncEventArgs(fileStatistics, syncedFileInfo, PreviewMode, (PreviewMode) ? "Analyzing changes to " : "Applying changes to " + fileBeingChanged);
                            OnFilesSyncingPreview(syncEvent);
                            PreviewedFiles.Add(syncedFileInfo);
                        }
                        else
                        {
                            // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                            // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                            // the stats reporting.
                            _currentSyncStats = fileStatistics;
                        }
                    }
                    break;
                case ChangeType.Delete:
                    if (syncType == FolderPairActionTypeEnum.Contribute)
                    {
                        VerboseOut("Skipping DELETE because in CONTRIBUTE mode there will be no deletion on both folders allowed.", false);
                        e.SkipChange = true;
                    }
                    else if (destinationDirectory == _currentFolderPair.RightFolder.Path)
                    {
                        // We should all the files being modified on the right. The destination comparison here means
                        // The the syncframework is tyring to modify part of the file in the right. So obviously it wants
                        // to copy the changes from the left to right which is the destination.
                        VerboseOut("Allowing DELETE on the RIGHT...", false);

                        if (PreviewMode)
                        {
                            GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo);
                            fileBeingChanged = syncedFileInfo.SourceFileName;
                            syncEvent = new FileSyncEventArgs(fileStatistics, syncedFileInfo, PreviewMode, "Analyzing changes to " + fileBeingChanged);
                            OnFilesSyncingPreview(syncEvent);
                            PreviewedFiles.Add(syncedFileInfo);
                        }
                        else
                        {
                            // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                            // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                            // the stats reporting.
                            GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo);
                            _currentSyncStats = fileStatistics;
                        }
                    }
                    else
                    {

                        VerboseOut("Skipping DELETE on the LEFT. All delete on the RIGHT will not be copied to the LEFT in ECHO.", false);
                        e.SkipChange = true;
                    }
                    break;
                default:
                    GetSyncedFileInfo(provider, e, _currentFolderPair, out fileStatistics, out syncedFileInfo); // Get various information on the current affected file
                    if (PreviewMode)
                    {
                        fileBeingChanged = syncedFileInfo.SourceFileName;
                        syncEvent = new FileSyncEventArgs(fileStatistics, syncedFileInfo, PreviewMode,
                                                          "Analyzing changes to " + fileBeingChanged);
                        OnFilesSyncingPreview(syncEvent);
                        PreviewedFiles.Add(syncedFileInfo);
                    }
                    else
                    {
                        // Since the sync is not running in preview mode, all sync event and progress reporting happens only when applying changes is done.
                        // Just save current stats and let the OnAppliedChange hanle the reporting so that the progress reporting will be in-sync with
                        // the stats reporting.
                        _currentSyncStats = fileStatistics;
                    }
                    break;

            }

        }

        /// <summary>
        /// Convenience method to get the calculated file synced statistics.
        /// This is where the number of files and folders that are being created and deleted get calculated.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="e"></param>
        protected virtual CrSyncFileStatistics GetSyncFileStatistics(FileSyncProvider provider, ApplyingChangeEventArgs e)
        {
            var currentChangeType = e.ChangeType;

            if ((_currentActionType == FolderPairActionTypeEnum.Contribute) && (currentChangeType == ChangeType.Delete))
            {
                e.SkipChange = true;
            }
            else if (!e.SkipChange)
            {
                switch (currentChangeType)
                {
                    case ChangeType.Create:
                        if (e.NewFileData.IsDirectory)
                        {

                            if (PreviewMode)
                            {
                                PreviewedCreatedFolders.Add(1, 0);
                            }
                            else
                            {
                                TotalCreatedFolders.Add(1, 0);
                            }
                        }
                        else
                        {

                            if (PreviewMode)
                            {
                                PreviewedCreatedFiles.Add(1, 0);
                            }
                            else
                            {
                                TotalCreatedFiles.Add(1, 0);
                            }
                        }
                        break;
                    case ChangeType.Delete:
                        if (_currentActionType != FolderPairActionTypeEnum.Contribute)
                        {
                            if (e.CurrentFileData != null)
                            {
                                if (e.CurrentFileData.IsDirectory)
                                {

                                    if (PreviewMode)
                                    {
                                        PreviewedDeletedFolders.Add(1, 0);
                                    }
                                    else
                                    {
                                        // We must check if current file data were already deleted. If it does then we should skip the count.
                                        if (_deletedObjects.ContainsKey(_currentFolderPair.UniqueIdentifier))
                                        {
                                            var deletedFolders = _deletedObjects[_currentFolderPair.UniqueIdentifier];
                                            if (!deletedFolders.Contains(e.CurrentFileData.RelativePath))
                                            {
                                                deletedFolders.Add(e.CurrentFileData.RelativePath); // Store deleted folder for reference
                                                _deletedObjects[_currentFolderPair.UniqueIdentifier] = deletedFolders;
                                                TotalDeletedFolders.Add(1, 0);

                                            }
                                        }
                                        else
                                        {
                                            var deletedFolders = new List<string> { e.CurrentFileData.RelativePath };
                                            _deletedObjects.Add(_currentFolderPair.UniqueIdentifier, deletedFolders);
                                            TotalDeletedFolders.Add(1, 0);

                                        }
                                    }
                                }
                                else
                                {
                                    // ------ FILES ---------------------------

                                    // Keeps track on the number of bytes being deleted
                                    _totalBytesDeleted += e.CurrentFileData.Size;

                                    if (PreviewMode)
                                    {
                                        PreviewedDeletedFiles.Add(1, 0);
                                    }
                                    else
                                    {
                                        TotalDeletedFiles.Add(1, 0);
                                    }
                                }
                            }
                            else
                            {
                                if (e.NewFileData.IsDirectory)
                                {
                                    if (PreviewMode)
                                    {
                                        PreviewedDeletedFolders.Add(1, 0);
                                    }
                                    else
                                    {
                                        TotalDeletedFolders.Add(1, 0);
                                    }
                                }
                                else
                                {
                                    // ---- FILES ------

                                    // Keeps track on the number of bytes being deleted
                                    _totalBytesDeleted += e.NewFileData.Size;

                                    if (PreviewMode)
                                    {
                                        PreviewedDeletedFiles.Add(1, 0);
                                    }
                                    else
                                    {
                                        TotalDeletedFiles.Add(1, 0);
                                    }
                                }
                            }
                        }
                        break;
                    case ChangeType.Rename:
                        if (PreviewMode)
                        {
                            PreviewedRenamedFiles.Add(1, 0);
                        }
                        else
                        {
                            TotalRenamedFiles.Add(1, 0);
                        }
                        break;
                    case ChangeType.Update:

                        // Prevent the stats from incrementing if the file is a metadata
                        var aFile = "";
                        if (e.CurrentFileData != null)
                        {
                            aFile = e.CurrentFileData.Name;
                        }
                        else if (e.NewFileData != null)
                        {
                            aFile = e.NewFileData.Name;
                        }

                        if ((aFile != _currentFolderPair.LeftFolder.FileName) || (aFile != _currentFolderPair.RightFolder.FileName))
                        {
                            if (PreviewMode)
                            {
                                PreviewedOverWrittenFiles.Add(1, 0);
                            }
                            else
                            {
                                TotalOverwrittenFiles.Add(1, 0);
                            }
                        }
                        break;
                    default:
                        break;
                }

            }


            // Prepare the file statistics
            CrSyncFileStatistics syncFileStatistics;

            if (PreviewMode)
            {
                syncFileStatistics = new CrSyncFileStatistics(PreviewedDeletedFolders,
                                                              PreviewedDeletedFiles,
                                                              PreviewedRenamedFiles,
                                                              PreviewedCreatedFolders,
                                                              PreviewedCreatedFiles,
                                                              PreviewedOverWrittenFiles,
                                                              PreviewedSkippedFiles,
                                                              TotalBytesToCopy);
            }
            else
            {
                syncFileStatistics = new CrSyncFileStatistics(TotalDeletedFolders,
                                                              TotalDeletedFiles,
                                                              TotalRenamedFiles,
                                                              TotalCreatedFolders,
                                                              TotalCreatedFiles,
                                                              TotalOverwrittenFiles,
                                                              TotalSkippedFiles,
                                                              TotalBytesToCopy);
            }

            return syncFileStatistics;

        }


        /// <summary>
        /// Calculate bytes that are to be copied...
        /// Note that we check each data ("Current","New"). Sometimes the sync framework only deals with current
        /// affected file, sometimes only the new affected file and sometimes both files.
        /// </summary>
        /// <param name="changeEventArgs"></param>
        /// <returns></returns>
        protected virtual long GetCurrentBytes(ApplyingChangeEventArgs changeEventArgs)
        {
            long currentFileDataSize = 0;

            if (changeEventArgs.ChangeType == ChangeType.Update)
            {
                if (changeEventArgs.NewFileData != null)
                {
                    TotalBytesToCopy += changeEventArgs.NewFileData.Size;
                    currentFileDataSize = changeEventArgs.NewFileData.Size;
                }
            }
            else if (changeEventArgs.CurrentFileData != null)
            {
                // Skip caculating affected bytes if the file is about get deleted...
                if ((changeEventArgs.ChangeType != ChangeType.Delete) && (changeEventArgs.ChangeType != ChangeType.Rename))
                {
                    if ((_currentFolderPair.LeftFolder.FileName != changeEventArgs.CurrentFileData.Name) || (_currentFolderPair.RightFolder.FileName != changeEventArgs.CurrentFileData.Name))
                    {
                        TotalBytesToCopy += changeEventArgs.CurrentFileData.Size;
                        currentFileDataSize = changeEventArgs.CurrentFileData.Size;
                    }
                }
            }
            else if (changeEventArgs.NewFileData != null)
            {
                if ((changeEventArgs.ChangeType != ChangeType.Delete) && (changeEventArgs.ChangeType != ChangeType.Rename))
                {
                    // Check if the change typ is delete because if it is delete there is no need to count the total bytes to copy.
                    if ((_currentFolderPair.LeftFolder.FileName != changeEventArgs.NewFileData.Name) || (_currentFolderPair.RightFolder.FileName != changeEventArgs.NewFileData.Name))
                    {
                        TotalBytesToCopy += changeEventArgs.NewFileData.Size;
                        currentFileDataSize = changeEventArgs.NewFileData.Size;
                    }
                }
            }

            return currentFileDataSize;
        }

        /// <summary>
        /// Helper method for generating the correct source to destination root directories and relative file paths.
        /// Since changeEventArgs deals relative files and root directories differently depending on change type and might  not
        /// provide information on which affected file is a source or a target, we must determine the correct relative files and folders
        /// ourselves based on the current provider and current folder pair. 
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="changeEventArgs"></param>
        /// <param name="folderPair"></param>
        /// <param name="sourceRootDirectory"></param>
        /// <param name="destinationRootDirectory"></param>
        /// <param name="sourceRelativeFilePath"></param>
        /// <param name="destinationRelativeFilePath"></param>
        /// <param name="sourceLastModified"></param>
        /// <param name="destinationLastModified"></param>
        /// <param name="sourceFileName"></param>
        /// <param name="destinationFileName"></param>
        protected virtual void GetAffectedFile(FileSyncProvider provider, ApplyingChangeEventArgs changeEventArgs, FileSyncPairInfo folderPair, out string sourceRootDirectory, out string destinationRootDirectory, out string sourceRelativeFilePath, out string destinationRelativeFilePath, out DateTime sourceLastModified, out DateTime destinationLastModified, out string sourceFileName, out string destinationFileName, out bool isDirectory)
        {
            // Get various information on the affected file

            var sourceRelativeFilePathTmp = changeEventArgs.CurrentFileData != null ? changeEventArgs.CurrentFileData.RelativePath : " ";
            var sourceLastModifiedTmp = changeEventArgs.CurrentFileData != null ? changeEventArgs.CurrentFileData.LastWriteTime : DateTime.MinValue;
            var sourceFileNameTmp = changeEventArgs.CurrentFileData != null ? changeEventArgs.CurrentFileData.Name : " ";

            var destinationRelativeFilePathTmp = changeEventArgs.NewFileData != null ? changeEventArgs.NewFileData.RelativePath : " ";
            var destinationLastModifiedTmp = changeEventArgs.NewFileData != null ? changeEventArgs.NewFileData.LastWriteTime : DateTime.MinValue;
            var destinationFileNameTmp = changeEventArgs.NewFileData != null ? changeEventArgs.NewFileData.Name : " ";

            var sourceRootDirectoryTmp = "";
            var destinationRootDirectoryTmp = "";

            // Get approriate root directory so that we can extract the correct affected file names,
            // otherwise all files will be in relative path and there will be no way to identify which is a source and a destination

            if (provider.RootDirectoryPath == folderPair.LeftFolder.Path)
            {
                sourceRootDirectoryTmp = folderPair.LeftFolder.Path;
                destinationRootDirectoryTmp = folderPair.RightFolder.Path;
            }
            else
            {
                sourceRootDirectoryTmp = folderPair.RightFolder.Path;
                destinationRootDirectoryTmp = folderPair.LeftFolder.Path;
            }


            // Identify the type of the affected object if it's a file or a folder
            if (changeEventArgs.CurrentFileData != null)
            {
                isDirectory = changeEventArgs.CurrentFileData.IsDirectory;
            }
            else if (changeEventArgs.NewFileData != null)
            {
                isDirectory = changeEventArgs.NewFileData.IsDirectory;
            }
            else
            {
                isDirectory = false;
            }

            // Depending on the change type we must provider the correct source file and destination file
            switch (changeEventArgs.ChangeType)
            {
                case ChangeType.Create:
                    /**************************************************************************************************************
                     * If sync framework detects a newly created  file or folder, the current file data is left blank
                     * therefore we automatically make the new file data also the current file data. That is make
                     * source relative file path the same with destination file path.
                     **************************************************************************************************************/

                    sourceRelativeFilePathTmp = destinationRelativeFilePathTmp;
                    sourceFileNameTmp = destinationFileNameTmp;


                    var temp = string.Copy(sourceRootDirectoryTmp);
                    sourceRootDirectoryTmp = string.Copy(destinationRootDirectoryTmp);
                    destinationRootDirectoryTmp = temp;



                    var temp2 = new DateTime(sourceLastModifiedTmp.Ticks);
                    sourceLastModifiedTmp = new DateTime(destinationLastModifiedTmp.Ticks);
                    destinationLastModifiedTmp = temp2;

                    /**************************************************************************************************************
                     * The affected file can also be a folder. The change event args treat everything as file but we do want
                     * to display it to the user that something they can understand. That is if the affected file is a folder,
                     * then make that either the source or the destination and leave the file name blank.
                     * 
                     * Also in SyncToy, if the affected file is a folder then they leave everything blank except for the TargetPath.
                     * 
                     * NOTE: Howerver on Delete action, everything is treated as a file...
                     **************************************************************************************************************/
                    ;

                    if (isDirectory)
                    {
                        sourceRootDirectoryTmp = "";
                        sourceFileNameTmp = "";
                        sourceRelativeFilePathTmp = "";

                        destinationRootDirectoryTmp = new FileNode(destinationRootDirectoryTmp).Join(destinationRelativeFilePathTmp).Path;
                        destinationFileNameTmp = "";
                        destinationRelativeFilePathTmp = "";

                        sourceLastModifiedTmp = DateTime.MinValue;
                        destinationLastModifiedTmp = DateTime.MinValue;
                    }


                    // Final result
                    sourceRootDirectory = sourceRootDirectoryTmp;
                    sourceRelativeFilePath = sourceRelativeFilePathTmp;
                    sourceFileName = sourceFileNameTmp;

                    destinationRootDirectory = destinationRootDirectoryTmp;
                    destinationRelativeFilePath = destinationRelativeFilePathTmp;
                    destinationFileName = destinationFileNameTmp;

                    sourceLastModified = sourceLastModifiedTmp;
                    destinationLastModified = destinationLastModifiedTmp;


                    break;
                case ChangeType.Delete:

                    // When deleting files and folders everything is treated as a target file or destination file
                    // regardless of the file origins but make the source info blank.
                    sourceLastModifiedTmp = DateTime.MinValue;
                    destinationLastModifiedTmp = DateTime.MinValue;

                    destinationFileNameTmp = string.Copy(sourceFileNameTmp);
                    destinationRelativeFilePathTmp = string.Copy(sourceRelativeFilePathTmp);
                    destinationRootDirectoryTmp = string.Copy(sourceRootDirectoryTmp);

                    sourceFileNameTmp = "";
                    sourceRelativeFilePathTmp = "";
                    sourceRootDirectoryTmp = "";

                    // Final result
                    sourceRootDirectory = sourceRootDirectoryTmp;
                    sourceRelativeFilePath = sourceRelativeFilePathTmp;
                    sourceFileName = sourceFileNameTmp;

                    destinationRootDirectory = destinationRootDirectoryTmp;
                    destinationRelativeFilePath = destinationRelativeFilePathTmp;
                    destinationFileName = destinationFileNameTmp;

                    sourceLastModified = sourceLastModifiedTmp;
                    destinationLastModified = destinationLastModifiedTmp;

                    break;
                case ChangeType.Update:
                    // Not sure but root directories are switched so we have to flip the values...
                    var tmpUpdate = string.Copy(sourceRootDirectoryTmp);
                    sourceRootDirectoryTmp = string.Copy(destinationRootDirectoryTmp);
                    destinationRootDirectoryTmp = tmpUpdate;

                    var tmpUpdate2 = new DateTime(sourceLastModifiedTmp.Ticks);
                    sourceLastModifiedTmp = destinationLastModifiedTmp;
                    destinationLastModifiedTmp = tmpUpdate2;

                    // Final result
                    sourceRootDirectory = sourceRootDirectoryTmp;
                    sourceRelativeFilePath = sourceRelativeFilePathTmp;
                    sourceFileName = sourceFileNameTmp;

                    destinationRootDirectory = destinationRootDirectoryTmp;
                    destinationRelativeFilePath = destinationRelativeFilePathTmp;
                    destinationFileName = destinationFileNameTmp;

                    sourceLastModified = sourceLastModifiedTmp;
                    destinationLastModified = destinationLastModifiedTmp;

                    break;
                case ChangeType.Rename:

                    // During the rename process,  no file or folder is being moved from the current folderpair's root directory to the other.
                    // All modification happens on the current source/destination's root directory. That is to make sure that the sources' root directory
                    // is the same as the destination's root directory.

                    destinationRootDirectoryTmp = sourceRootDirectoryTmp;

                    // Final result
                    sourceRootDirectory = sourceRootDirectoryTmp;
                    sourceRelativeFilePath = sourceRelativeFilePathTmp;
                    sourceFileName = sourceFileNameTmp;

                    destinationRootDirectory = destinationRootDirectoryTmp;
                    destinationRelativeFilePath = destinationRelativeFilePathTmp;
                    destinationFileName = destinationFileNameTmp;

                    sourceLastModified = sourceLastModifiedTmp;
                    destinationLastModified = destinationLastModifiedTmp;


                    break;
                default:
                    //DO NOTHING

                    // Final result
                    sourceRootDirectory = sourceRootDirectoryTmp;
                    sourceRelativeFilePath = sourceRelativeFilePathTmp;
                    sourceFileName = sourceFileNameTmp;

                    destinationRootDirectory = destinationRootDirectoryTmp;
                    destinationRelativeFilePath = destinationRelativeFilePathTmp;
                    destinationFileName = destinationFileNameTmp;

                    sourceLastModified = sourceLastModifiedTmp;
                    destinationLastModified = destinationLastModifiedTmp;

                    break;

            }
        }

        /// <summary>
        /// Get information on the current synced file, the actions taken and statistical data.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="changeEventArgs"></param>
        /// <param name="folderPair"></param>
        /// <param name="fileStatistics"></param>
        /// <param name="syncedFileInfo"></param>
        /// <param name="calculateStatistics">Default is true. If Set to FALSE no statistical calculation will be performed.</param>
        protected virtual void GetSyncedFileInfo(FileSyncProvider provider, ApplyingChangeEventArgs changeEventArgs, FileSyncPairInfo folderPair, out CrSyncFileStatistics fileStatistics, out CrSyncedFileInfo syncedFileInfo, bool calculateStatistics = true)
        {
            // Get various information on the affected file
            string sourceRootDirectory;
            string sourceRelativeFilePath;
            DateTime sourceDateLastModified;
            string sourceFileName;

            string destinationRootDirectory;
            string destinationRalativeFilePath;
            DateTime destinationDateLastModified;
            string destinationFileName;

            bool isDirectory;

            // get info on the affected file
            GetAffectedFile(provider,
                            changeEventArgs,
                            folderPair,
                            out sourceRootDirectory,
                            out destinationRootDirectory,
                            out sourceRelativeFilePath,
                            out destinationRalativeFilePath,
                            out sourceDateLastModified,
                            out destinationDateLastModified,
                            out sourceFileName,
                            out destinationFileName,
                            out isDirectory);

            var sourceLastModified = (sourceDateLastModified == DateTime.MinValue) ? "--:--" : string.Format("{0:MM/dd/yyyy hh:mm:ss tt}", sourceDateLastModified);
            var destinationLastModified = (destinationDateLastModified == DateTime.MinValue) ? "--:--" : string.Format("{0:MM/dd/yyyy hh:mm:ss tt}", destinationDateLastModified);

            VerboseOut("Perform applying change to SOURCE = " + sourceFileName + ", DESINATION = " + destinationFileName, false);


            if (ShouldSkipSourceSyncedFileInfoForIdentifier(sourceRelativeFilePath, _currentFolderPair.UniqueIdentifier))
            {
                // Skipping the current relative file path from source
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[1] Skipping statistics because it's being blacklisted", false);
                if ((!isDirectory) && (changeEventArgs.ChangeType == ChangeType.Delete))
                {
                    _skippedFilesCounter++;
                }
                _skippedOprationsCounter++;
            }
            else if (ShouldSkipDestinationSyncedFileInfoForIdentifier(destinationRalativeFilePath, _currentFolderPair.UniqueIdentifier))
            {
                // Skipping the current relative file path from destination
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[2] Skipping statistics because it's being blacklisted", false);
                if ((!isDirectory) && (changeEventArgs.ChangeType == ChangeType.Delete))
                {
                    _skippedFilesCounter++;
                }
                _skippedOprationsCounter++;
            }
            else if (ShouldSkipSubfolderAtRelativePath(sourceRelativeFilePath))
            {
                // Skipping the current relative subfolder from source
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[3A] Skipping source subfolder folder " + sourceRelativeFilePath, false);
            }
            else if (ShouldSkipSubFolderAtPath(sourceRootDirectory))
            {
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[3B] Skipping source subfolder " + sourceRootDirectory, false);
            }
            else if (ShouldSkipSubfolderAtRelativePath(destinationRalativeFilePath))
            {
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[4A] Skipping source subfolder " + destinationRalativeFilePath, false);
            }
            else if (ShouldSkipSubFolderAtPath(destinationRootDirectory))
            {
                changeEventArgs.SkipChange = true;
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("[4B] Skipping source subfolder " + destinationRootDirectory, false);
            }
            else if (!changeEventArgs.SkipChange)
            {

                // Prepare information on the affected/synced file
                syncedFileInfo = new CrSyncedFileInfo(_currentFolderPair,
                                                      sourceRootDirectory,
                                                      sourceRelativeFilePath,
                                                      sourceLastModified,
                                                      destinationRootDirectory,
                                                      destinationRalativeFilePath,
                                                      destinationLastModified,
                                                      changeEventArgs.ChangeType,
                                                      GetCurrentBytes(changeEventArgs))
                {
                    SourceFileName = sourceFileName,
                    DestinationFileName = destinationFileName
                };

                if (changeEventArgs.CurrentFileData != null)
                {
                    syncedFileInfo.IsDirectory = changeEventArgs.CurrentFileData.IsDirectory;
                }
                else if (changeEventArgs.NewFileData != null)
                {
                    syncedFileInfo.IsDirectory = changeEventArgs.NewFileData.IsDirectory;
                }


                var currentNode = new FileNode(provider.RootDirectoryPath).Join(sourceRelativeFilePath);

                if (provider == _sourceProvider)
                {
                    syncedFileInfo.SourceProvider = provider;
                    syncedFileInfo.DestinationProvider = _destinationProvider;
                }
                else
                {
                    syncedFileInfo.SourceProvider = _destinationProvider;
                    syncedFileInfo.DestinationProvider = provider;
                }


                if (PreviewMode)
                {

                    VerboseOut(string.Format("Inpecting {0} / {1}", sourceRootDirectory, destinationRootDirectory), false);

                    //Count the actions taken while in preview but prevent from incrementing if the file is a metadata;
                    if ((sourceRelativeFilePath != _currentFolderPair.LeftFolder.FileName) || (sourceRelativeFilePath != _currentFolderPair.RightFolder.FileName) && calculateStatistics)
                    {
                        PreviewedActionsTaken++;
                        if (Path.HasExtension(currentNode.Path))
                        {
                            _totalFileActionsTaken++;
                        }
                    }

                }
                else
                {
                    if (calculateStatistics)
                    {
                        TotalActionsTaken++;
                    }
                }

                fileStatistics = calculateStatistics ? GetSyncFileStatistics(provider, changeEventArgs) : null;
            }
            else
            {
                fileStatistics = null;
                syncedFileInfo = null;
                VerboseOut("Skipping statistics... but why?", false);
            }
        }

        /// <summary>
        /// Callback method for actual file/folder modification.
        /// This callback gets called only during actual synchronization process or when PreviewMode is set to false
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        protected virtual void CallBackFileProviderAppliedChange(object sender, AppliedChangeEventArgs args)
        {
            var currentProvider = sender as FileSyncProvider;

            var appliedChangedMessage = "";
            var currentFileName = "";
            var currentFile = "";

            if (currentProvider != null)
            {
                var fileNode = new FileNode(currentProvider.RootDirectoryPath);
                if (args.NewFilePath != null)
                {
                    currentFileName = args.NewFilePath;
                    fileNode.Join(args.NewFilePath);
                }
                else if (args.OldFilePath != null)
                {
                    currentFileName = args.OldFilePath;
                    fileNode.Join(args.OldFilePath);
                }

                var currentFilePath = fileNode.Path;

                currentFile = new FileNode(currentFilePath).GetFileName();


            }


            // Update stats about the current files being changed but prevent from incrementing when the file is a metadata
            if ((currentFileName != _currentFolderPair.LeftFolder.FileName) || (currentFileName != _currentFolderPair.LeftFolder.FileName))
            {
                _filesChangedCounter++;

                // Calculate percentage value
                if (PreviewedActionsTaken == 0)
                {
                    ProgressValue = 0;
                }
                else
                {
                    var fraction = _filesChangedCounter / (double)PreviewedActionsTaken;
                    ProgressValue = Convert.ToInt32(fraction * 100);
                }

                // Update progress percentage 
                var fileSyncEventArgs = new FileSyncEventArgs(new CrSyncFileStatistics(ProgressValue), PreviewMode);
                OnFilesSyncing(fileSyncEventArgs);

                VerboseOut(string.Format(" PROGRESS : TOTAL FILE = {0} / CURRENT FILE {1} / PERCENT {2}", PreviewedActionsTaken, _filesChangedCounter, ProgressValue), false);

            }



            VerboseOut("Applying changes to " + (args.OldFilePath ?? "_NULL_"), false);
            switch (args.ChangeType)
            {
                case ChangeType.Create:
                    appliedChangedMessage = "Done copying " + currentFile;
                    VerboseOut(string.Format("        {0} Copied", currentFile), true);
                    break;
                case ChangeType.Delete:
                    appliedChangedMessage = "Done deleting " + currentFile;
                    VerboseOut(string.Format("        {0} Deleted", currentFile), true);
                    break;
                case ChangeType.Update:
                    appliedChangedMessage = "Done updating " + currentFile;
                    VerboseOut(string.Format("        {0} Updated", currentFile), true);
                    break;
                case ChangeType.Rename:
                    appliedChangedMessage = "Done ranaming " + currentFile;
                    VerboseOut(string.Format("        {0} Renamed", currentFile), true);
                    break;
            }

            // Fire an Event but prevent from firin when the file is a metadata
            if ((currentFileName != _currentFolderPair.LeftFolder.FileName) || (currentFileName != _currentFolderPair.RightFolder.FileName))
            {
                // fire message
                var syncEvent = new FileSyncEventArgs(appliedChangedMessage, PreviewMode);
                syncEvent.FileSyncStatistics = _currentSyncStats;
                OnFilesSyncing(syncEvent);
            }


            //Check for cancellation
            if (CancelPending)
            {
                VerboseOut("Cancelled by user!", true);
                _syncOrchestrator.Cancel();
            }


            // All changes have been applied to the current item so we must clear whatever info that is in the current conflicting item...
            _sourceConflictingItem = null;
            _destinationConflictingItem = null;
        }


        public int TotalFilesUntouched
        {
            get
            {
                var untouched = TotalFilesFound - _totalFileActionsTaken;
                untouched = PreviewMode
                                ? untouched - PreviewedDeletedFiles.Success
                                : untouched - TotalDeletedFiles.Success;

                return (untouched - _skippedFilesCounter);
            }
        }

        public int TotalSkippedOperations
        {
            get { return _skippedOprationsCounter; }
        }

        public long AvoidedBytes
        {
            get
            {
                var avoidedBytes = TotalFileSize - TotalBytesToCopy;
                avoidedBytes = avoidedBytes - _totalBytesDeleted;
                return avoidedBytes;
            }
        }

        /// <summary>
        /// Convenience method for identifying if the file given has been selected for exclusion.
        /// The exclusion list is based on the synced file info being blacklisted using the Preview dialog.
        /// </summary>
        /// <param name="relativePath">A file path relative to the root directory. This does not necessarily mean a filename. </param>
        /// <param name="folderPairIdentifier"></param>
        /// <returns></returns>
        protected virtual bool ShouldSkipSourceSyncedFileInfoForIdentifier(string relativePath, string folderPairIdentifier)
        {
            // Check if the file is blacklisted
            if ((!string.IsNullOrEmpty(relativePath)) && (CrSynchedFileInfoToExclude != null))
            {
                // Do not change to for-each loop. Will throw "COLLECTION WAS MODIFIED" exception
                for (int index = 0; index < CrSynchedFileInfoToExclude.Count; index++)
                {
                    var syncedFileInfo = CrSynchedFileInfoToExclude[index];
                    if ((syncedFileInfo.FolderPair.UniqueIdentifier == folderPairIdentifier) &&
                        (syncedFileInfo.SourceRelativeFilePath == relativePath))
                    {
                        VerboseOut(
                            "Skipping source relative path " + relativePath + " from " +
                            syncedFileInfo.SourceRootDirectory + " of " + syncedFileInfo.FolderPair.FolderPairName,
                            false);
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Convenience method for identifying if the file given has been selected for exclusion.
        /// The exclusion list is based on the synced file info being blacklisted using the Preview dialog.
        /// </summary>
        /// <param name="relativePath"></param>
        /// <param name="folderPairIdentifier"></param>
        /// <returns></returns>
        protected virtual bool ShouldSkipDestinationSyncedFileInfoForIdentifier(string relativePath, string folderPairIdentifier)
        {
            // Check if the file is blacklisted
            if (!string.IsNullOrEmpty(relativePath))
            {
                foreach (var syncedFileInfo in
                    CrSynchedFileInfoToExclude.Where(syncedFileInfo => syncedFileInfo.FolderPair.UniqueIdentifier == folderPairIdentifier && syncedFileInfo.DestinationRelativeFilePath == relativePath))
                {
                    VerboseOut("Skipping destination relative path " + relativePath + " from " + syncedFileInfo.DestinationRootDirectory + " of " + syncedFileInfo.FolderPair.FolderPairName, false);
                    return true;
                }
            }
            return false;
        }


        /// <summary>
        /// Convenience method for identifying if the given path should be skipped.
        /// </summary>
        /// <param name="relativeSubfolderPath"></param>
        /// <returns></returns>
        protected virtual bool ShouldSkipSubfolderAtRelativePath(string relativeSubfolderPath)
        {
            if (string.IsNullOrEmpty(relativeSubfolderPath))
            {
                return false;
            }
            if (_currentFolderPair.IsSubFolderExcluded(relativeSubfolderPath))
            {
                VerboseOut("Skipping subfolder " + relativeSubfolderPath + " as defined by folderpair's exclusion lists.", false);
                return true;
            }
            return false;
        }


        /// <summary>
        /// Convenience method for identifying if the given path should be skipped.
        /// </summary>
        /// <param name="absolutePath"></param>
        /// <returns></returns>
        protected virtual bool ShouldSkipSubFolderAtPath(string absolutePath)
        {
            if (string.IsNullOrEmpty(absolutePath))
            {
                return false;
            }
            var fileNode = new FileNode(absolutePath);
            var relativePath = fileNode.GetRelativePathFromFolderPair(_currentFolderPair);
            return ShouldSkipSubfolderAtRelativePath(relativePath);
        }


        public virtual bool SkipFolderPairWithUid(string uid)
        {
            if (_folderPairsToSkip.Contains(uid))
            {
                return false;
            }
            _folderPairsToSkip.Add(uid);
            return true;
        }

        /// <summary>
        /// Only gets called whenever a file of the same name is in conflict on both location. The sync framework seems to
        /// handle conflicting items automatically on a normal sync so all the codes here are written for Echo and Contribute.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        protected virtual void CallBackFileProviderItemConflicting(object sender, ItemConflictingEventArgs args)
        {
            VerboseOut("Conflicting item in process...", false);
            _currentItemIsInConflict = true;
            IFileDataRetriever sourceDataRetriever = null;
            IFileDataRetriever destinationDataRetriever = null;
            var sourceChangeKind = args.SourceChange.ChangeKind;
            var destinationChangeKind = args.DestinationChange.ChangeKind;
            var sourceItemId = args.SourceChange.ItemId;
            var destinationItemId = args.DestinationChange.ItemId;
            var sourceAbsolutePath = "";
            var destinationAbsolutePath = "";
            var sourceRelative = "";
            var destinationRelative = "";

            try
            {
                sourceDataRetriever = (args.SourceChangeData != null)
                                          ? (IFileDataRetriever)args.SourceChangeData
                                          : null;

                if (sourceDataRetriever != null) sourceAbsolutePath = sourceDataRetriever.AbsoluteSourceFilePath;
                if (sourceDataRetriever != null) sourceRelative = sourceDataRetriever.FileData.RelativePath;
            }
            catch (ArgumentOutOfRangeException argumentOutOfRangeException)
            {
                VerboseOut("Unable to get SOURCE CHANGE DATA on conflicting item. " + argumentOutOfRangeException.Message, false);
            }

            try
            {
                destinationDataRetriever = (args.DestinationChangeData != null)
                                               ? (IFileDataRetriever)args.DestinationChangeData
                                               : null;
                if (destinationDataRetriever != null) destinationAbsolutePath = destinationDataRetriever.AbsoluteSourceFilePath;
                if (destinationDataRetriever != null) destinationRelative = destinationDataRetriever.FileData.RelativePath;
            }
            catch (ArgumentOutOfRangeException argumentOutOfRangeException2)
            {
                VerboseOut("Unable to get DESTINATION CHANGE DATA on conflicting item. " + argumentOutOfRangeException2.Message, false);
            }

            var sourceNode = (sourceDataRetriever != null) ? new FileNode(sourceDataRetriever.AbsoluteSourceFilePath) : null;
            var destinationNode = (destinationDataRetriever != null) ? new FileNode(destinationDataRetriever.AbsoluteSourceFilePath) : null;


            var sourceRoot = (sourceNode != null)
                             ? sourceNode.IsChildOf(_currentFolderPair.LeftFolder.Path)
                                   ? _currentFolderPair.LeftFolder.Path
                                   : _currentFolderPair.RightFolder.Path
                             : string.Empty;

            var destinationRoot = (destinationNode != null)
                                  ? destinationNode.IsChildOf(_currentFolderPair.LeftFolder.Path)
                                        ? _currentFolderPair.LeftFolder.Path
                                        : _currentFolderPair.RightFolder.Path
                                  : null;

            VerboseOut("Item conflicting found. ", false);
            VerboseOut("[ SOURCE CHANGES ] KIND = " + sourceChangeKind + ", ID = " + sourceItemId + ", PATH = " + sourceAbsolutePath + ", ROOT = " + sourceRoot + ", RELATIVE = " + sourceRelative, false);
            VerboseOut("[ DESTINATION CHANGES ] KIND = " + destinationChangeKind + ", ID = " + destinationItemId + ", PATH = " + destinationAbsolutePath + ", ROOT = " + destinationRoot + ", RELATIVE = " + destinationRelative, false);


            // Store the conflicting item temporarily. This delegate is triggered twice bu we are only interested on the first occurence of this delegate... 
            if (_sourceConflictingItem != null)
            {
                if (_sourceConflictingItem.Identifier != null && !_sourceConflictingItem.Identifier.Equals(args.SourceChange.ItemId))
                {
                    _sourceConflictingItem = new CrSyncConflictingItem(args.SourceChange.ItemId, sourceDataRetriever) { RootDirectoryPath = sourceRoot, ChangeType = args.SourceChange.ChangeKind };
                    VerboseOut("This SOURCE conflicting item is now cached... ", false);
                }
                else
                {
                    VerboseOut("This SOURCE conflicting item is NO longer cached... ", false);
                }

            }
            else
            {
                _sourceConflictingItem = new CrSyncConflictingItem(args.SourceChange.ItemId, sourceDataRetriever) { RootDirectoryPath = sourceRoot, ChangeType = args.SourceChange.ChangeKind };
                VerboseOut("This SOURCE conflicting item is now cached... ", false);
            }

            if (_destinationConflictingItem != null)
            {
                if (_destinationConflictingItem.Identifier != null)
                {
                    if (!_destinationConflictingItem.Identifier.Equals(args.DestinationChange.ItemId))
                    {
                        _destinationConflictingItem = new CrSyncConflictingItem(args.DestinationChange.ItemId, destinationDataRetriever) { RootDirectoryPath = destinationRoot, ChangeType = args.DestinationChange.ChangeKind };
                        VerboseOut("This DESTINATION conflicting item is now cached... ", false);
                    }
                    else
                    {
                        VerboseOut("This DESTINATION conflicting item is NOT cached... ", false);
                    }
                }

            }
            else
            {
                _destinationConflictingItem = new CrSyncConflictingItem(args.DestinationChange.ItemId, destinationDataRetriever) { RootDirectoryPath = destinationRoot, ChangeType = args.DestinationChange.ChangeKind };
                VerboseOut("This DESTINATION conflicting item is now cached... ", false);
            }


            // Set resolution
            if ((_currentFolderPair.FolderPairActionType == FolderPairActionTypeEnum.Echo) || (_currentFolderPair.FolderPairActionType == FolderPairActionTypeEnum.Contribute))
            {
                if ((args.SourceChange.ChangeKind == ChangeKind.Update) || (args.SourceChange.ChangeKind == ChangeKind.Deleted) || (args.SourceChange.ChangeKind == ChangeKind.Ghost))
                {
                    if (sourceRoot == _currentFolderPair.RightFolder.Path)
                    {
                        args.SetResolutionAction(ConflictResolutionAction.DestinationWins);
                        VerboseOut("Found conflicting item with with the right folder. Left folder will take over...", false);
                    }
                    else
                    {
                        args.SetResolutionAction(ConflictResolutionAction.SourceWins);
                        VerboseOut("Found conflicting item with the left folder. Left folder always take over...", false);
                    }


                }
                else if (args.SourceChange.ChangeKind == ChangeKind.UnknownItem)
                {
                    args.SetResolutionAction(ConflictResolutionAction.SkipChange);
                    VerboseOut("Skipping conflicing items because the item in question is unknown to the replica...",true);
                }
                else
                {
                    VerboseOut("[WARNING]: Unhandled conflicting items "+args.SourceChange.ChangeKind+"...", true);
                }
            }

        }
    }
}

//---


